package ar.edu.unlp.info.tesis.database.service;

import java.util.Collection;
import java.util.List;
import java.util.Vector;

import org.hibernate.HibernateException;
import org.hibernate.criterion.Criterion;
import org.hibernate.type.Type;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import ar.edu.unlp.info.tesis.database.BDUtils;
import ar.edu.unlp.info.tesis.database.GenericRepository;
import ar.edu.unlp.info.tesis.database.exception.DatabaseException;
import ar.edu.unlp.info.tesis.database.operator.AliasCriteria;
import ar.edu.unlp.info.tesis.database.operator.BetweenRestriction;
import ar.edu.unlp.info.tesis.database.operator.CompareFieldsRestriction;
import ar.edu.unlp.info.tesis.database.operator.EqualRestriction;
import ar.edu.unlp.info.tesis.database.operator.GenericControlerOperator;
import ar.edu.unlp.info.tesis.database.operator.GreaterEqualRestriction;
import ar.edu.unlp.info.tesis.database.operator.GreaterRestriction;
import ar.edu.unlp.info.tesis.database.operator.InsideCollectionRestriction;
import ar.edu.unlp.info.tesis.database.operator.IsNotNullRestriction;
import ar.edu.unlp.info.tesis.database.operator.LeastEqualRestriction;
import ar.edu.unlp.info.tesis.database.operator.LeastRestriction;
import ar.edu.unlp.info.tesis.database.operator.LikeRestriction;
import ar.edu.unlp.info.tesis.database.operator.OperatorRestriction;
import ar.edu.unlp.info.tesis.database.operator.QueryOperator;
import ar.edu.unlp.info.tesis.database.operator.SqlRestriction;
import ar.edu.unlp.info.tesis.database.operator.TempGreaterEqualStringRestriction;
import ar.edu.unlp.info.tesis.database.operator.TempLeastEqualStringRestriction;
import ar.edu.unlp.info.tesis.domain.Entidad;

@Service
@Transactional
public class GenericService implements GenericRepository{

	private Class clazz;

	private GenericControlerOperator gco;
	
	QueryOperator currentQueryOperator = null;
	
	
	protected Class getClazz() {
		return clazz;
	}

	protected void setClazz(Class clazz) {
		this.clazz = clazz;
	}


	public static GenericService getService(Class clazz) {
		GenericService service = new GenericService();
		service.setClazz(clazz);
		return service;
	}

	
	public GenericControlerOperator getGco() {
		if(gco==null) gco = new GenericControlerOperator(this.getClazz());
		return gco;
	}

	protected void setGco(GenericControlerOperator gco) {
		this.gco = gco;
	}
	
	
	
	@Override
	public void save(Object entity) {
		BDUtils.getSession().save(entity);
	}

	@Override
	public void update(Object entity) {
		BDUtils.getSession().save(entity);
	}
	
	

	@Override
	public void remove(Object entity) {
		BDUtils.getSession().delete(entity);
	}
	

	public Entidad saveWithOutFlush(Entidad persistent)
			throws DatabaseException {
		this.getGco().saveOrUpdate(persistent);
		return persistent;
	}
	
	/**Fin Metodos Actualizacion Datos**/

	
	/**Metodos configuacion controlador**/	

	public void setFirstResult(int firstResult) {
		this.getGco().setFirstResult(firstResult);
	}

	public void setMaxResult(int maxResult) {
		this.getGco().setMaxResult(maxResult);
	}
	
	public GenericService addOrderParam(String field, boolean orderASC) {
		this.getGco().addOrderParam(field, orderASC);
		return this;
	}

	public GenericService cleanOrderParam() {
		this.getGco().getOrderParams().clear();
		return this;
	}
	
	
	@SuppressWarnings("unchecked")
	public void setOrderParams(boolean orderASC, String... orderParams) {
		List<String> lista = new Vector<String>();
		for (int i = 0; i < orderParams.length; i++) {
			lista.add(orderParams[i]);
		}

		this.getGco().addOrderParams(lista, orderASC);
	}
	
	
	public GenericService addAlias(String atributo, String alias) {
		List<AliasCriteria> listAliasCriteria = this.getGco().getQueryOperator().getAliasList();
		AliasCriteria.addAlias(listAliasCriteria, atributo, alias, null);
		return this;
	}
	
	
	/**
	 * 
	 * @param atributo
	 * @param alias
	 * @param joinType
	 *            : INNER_JOIN,,FULL_JOIN, LEFT_OUTER_JOIN, RIGHT_OUTER_JOIN
	 * @return
	 */
	public GenericService addAlias(String atributo, String alias,
			String joinType) {
		List<AliasCriteria> listAliasCriteria = this.getGco()
				.getQueryOperator().getAliasList();
		AliasCriteria.addAlias(listAliasCriteria, atributo, alias, joinType);
		return this;
	}


	public GenericService addAlias(String aliases) {

		String[] fields = aliases.split("\\.");
		String alias = aliases;

		if (fields.length > 1) {
			String field = "";
			int i = 0;
			for (i = 0; i < fields.length;) {
				alias = fields[i];
				field = field + fields[i];
				this.addAlias(field, alias);
				field = alias + ".";
				i++;
			}
		} else
			this.addAlias(aliases, aliases);

		return this;
	}
	
	
	public GenericService setOrderParams(List<String> orderParams,boolean asc) {
		this.getGco().addOrderParams(orderParams, asc);
		return this;
	}

	public GenericService setDISTINCT_ROOT_ENTITY(boolean distinct) {
		this.getGco().setDistinctEntity(distinct);
		return this;
	}
	
	

	@SuppressWarnings("unchecked")
	public GenericService reset() {
		this.getGco().resetDAO();
		this.currentQueryOperator = this.getGco().getQueryOperator();
		return this;
	}

	public GenericService resetOrderParams() {
		this.getGco().resetOrderParams();
		return this;

	}

	
	/**Fin Metodos configuacion controlador**/

	
	
	
	
	
	
	/**Metodos recuperacion Datos Individuales**/	

	@Override
	public Entidad findByProperty(String property, Object value) {
		return (Entidad)this.getGco().findByProperty( property,  value);
	}
	
	public Entidad uniqueResult() throws HibernateException {
		return this.getGco().uniqueResult();
	}


	public Entidad getByProperty(String field, Object property)
			throws HibernateException, DatabaseException {
		this.addEqualRestriction(field, property, false);
		return this.uniqueResult();
	}

	
	public final Entidad getById(long id) {
		return this.getGco().getById(id);
	}



	public final Entidad getById(String id) {
		return this.getGco().getByStringId(id);
	}
	
	/**Fin Metodos recuperacion Datos Individuales**/	




	/**Metodos recuperacion Datos Grupales**/		
	

	public List getList() throws HibernateException {
		return this.getGco().getList();
	}

	public List next(int init, int lenght) throws HibernateException {
		return this.getGco().next(init, lenght);
	}
	
	
	@Override
	public List findByCriteria(Criterion... crit) {		
		return this.getGco().findByCriteria(crit);
	}
	
	
	/**Fin Metodos recuperacion Datos Grupales**/	


	
	
	/**Metodos Calculos**/	
	

	public long recordSize() throws HibernateException {
		return this.getGco().getSize();
	}



	public Object getMax(String field) throws HibernateException {
		Object max = this.getGco().getMax(field);
		if(max==null) return 0;
		return max;
	}

	public Object getMax(Class clase, String field)
			throws InstantiationException, IllegalAccessException,
			HibernateException {
		Object max = this.getGco().getMax(field);
		if (max == null)
			max = clase.newInstance();
		return max;
	}

	public Object getMin(String field) throws HibernateException {
		return this.getGco().getMin(field);
	}

	public Double getCount(String field) throws HibernateException {
		Number valor = (Number) this.getGco().getCount(field);
		Double valor1 = new Double(0);
		try {
			valor1 = (Double) valor.doubleValue();
		} catch (Exception e) {
		}
		return valor1;
	}

	public Double getCountDistinct(String field) throws HibernateException {
		Number valor = (Number) this.getGco().getCountDistinct(field);
		Double valor1 = new Double(0);
		try {
			valor1 = (Double) valor.doubleValue();
		} catch (Exception e) {
		}
		return valor1;
	}

	public Double getSum(String field) throws HibernateException {
		Number valor = (Number) this.getGco().getSum(field);
		Double valor1 = new Double(0);

		try {
			valor1 = (Double) valor.doubleValue();
		} catch (Exception e) {
		}
		return valor1;
	}
	
	public double getSize() throws HibernateException {
		return this.getGco().getSize();
	}
	
	
	/** Fin Metodos Calculos**/
	
	
	

	/**Metodos Configuracion Operadores**/	


	public QueryOperator getQueryOperator() {
		if (currentQueryOperator == null)
			currentQueryOperator = this.getGco().getQueryOperator();
		return currentQueryOperator;
	}

	public QueryOperator getMainQueryOperator() {
		currentQueryOperator = this.getGco().getQueryOperator();
		return currentQueryOperator;
	}

	public QueryOperator getSubQueryOperator(String campo) {
		currentQueryOperator = this.getQueryOperator().getSubQueryOperator(
				campo);
		return currentQueryOperator;
	}

	public QueryOperator addSubQueryOperator(String campo) {
		currentQueryOperator = this.getQueryOperator().addSubQueryOperator(
				campo);
		return currentQueryOperator;
	}

	/**
	 * @author luis genera una nueva disjuncion a la consulta, es decir dentro
	 *         de las restriciones agrega un grupo OR
	 * 
	 *         POR EJ: from marca where (codigo = 2 OR nombre = 5) -- disjuntion
	 *         1 AND (codigo = 5 OR id = 10) -- disjuntion 2
	 * 
	 */

	public void createDisjuntion() {
		this.getQueryOperator().createDisjuntion();
	}

	public void createConjuntion() {
		this.getQueryOperator().createConjunction();
	}

	public void closeConjunction() {
		this.getQueryOperator().setCurrentConjuntion(null);
	}

	public void createProjGroupProperty(String field, String alias) {
		this.getQueryOperator().createProjGroupProperty(field, alias);
	}

	public void createProjCount(String field, String alias) {
		this.getQueryOperator().createProjCount(field, alias);
	}

	public void createProjCountDistinct(String field, String alias) {
		this.getQueryOperator().createProjCountDistinct(field, alias);
	}

	public void createProjMax(String field, String alias) {
		this.getQueryOperator().createProjMax(field, alias);
	}

	public void createProjMin(String field, String alias) {
		this.getQueryOperator().createProjMin(field, alias);
	}

	public void createProjSum(String field, String alias) {
		this.getQueryOperator().createProjSum(field, alias);
	}

	/***************************************************************************
	 * 
	 * @param sql
	 *            : sql query
	 * @param alias
	 *            : columnAliases
	 * @param types
	 *            :All Known Implementing Classes: AbstractBynaryType,
	 *            AbstractType, AnyType, BigIntegerType, BooleanType, ByteType,
	 *            CalendarType, CharacterType, CollectionType, ComponentType,
	 *            CompositeCustomType, CustomType, DateType, DbTimestampType,
	 *            EntityType, IntegerType, LongType, ShortType, StringType,
	 *            TimestampType
	 */

	public void createProjSQL(String sql, String[] alias, Type[] types) {

		this.getQueryOperator().createProjSQL(sql, alias, types);
	}

	public GenericService createProjProperty(String field, String alias) {
		this.getQueryOperator().createProjProperty(field, alias);
		return this;
	}

	public void cleanTemporalRestriction() {
		this.getQueryOperator().cleanTemporalRestriction();
	}

	public void createTemporalDisjuntion() {
		this.getQueryOperator().createTemporalDisjuntion();
	}

	public GenericService addRestriction(
			OperatorRestriction restriction, boolean currentDisjuntion) {
		this.getQueryOperator().addRestriction(restriction, currentDisjuntion);
		return this;
	}

	public GenericService addTemporalRestriction(
			OperatorRestriction restriction, boolean currentDisjuntion) {
		this.getQueryOperator().addTemporalRestriction(restriction,
				currentDisjuntion);

		return this;
	}

	/**
	 * metodo que invierte el tipo de restriccion, es decir un equal lo
	 * convierte en un not equal
	 * 
	 * @param ais
	 * @return
	 */
	public GenericService addNegativeRestriction(GenericService ais) {
		ais.getQueryOperator().getLastOperatorRestriction().setNegativeOperator(true);
		return ais;
	}

	public GenericService addEqualRestriction(String field,	Object value, boolean currentDisjuntion) {
		this.getQueryOperator().addRestriction(	new EqualRestriction(field, value), currentDisjuntion);
		return this;
	}

	/**
	 * Comparator Values: equal:e, leastEqual: le, least:lt; greatherEqual:ge,
	 * greather: gt;
	 * 
	 */

	public GenericService addCompareFieldRestriction(String field1,
			String field2, boolean currentDisjuntion, String comparator) {
		this.getQueryOperator().addRestriction(
				new CompareFieldsRestriction(field1, field2, comparator),
				currentDisjuntion);
		return this;
	}

	public GenericService addGreaterRestriction(String field,
			Object value, boolean currentDisjuntion) {
		this.getQueryOperator().addRestriction(
				new GreaterRestriction(field, value), currentDisjuntion);
		return this;
	}

	public GenericService addLeastEqualRestriction(String field,
			Object value, boolean currentDisjuntion) {
		this.getQueryOperator().addRestriction(
				new LeastEqualRestriction(field, value), currentDisjuntion);
		return this;
	}

	public GenericService addGreaterEqualStringRestriction(String field,
			Object value, boolean currentDisjuntion) {
		this.getQueryOperator().addRestriction(
				new TempGreaterEqualStringRestriction(field, value),
				currentDisjuntion);
		return this;
	}

	public GenericService addLeastEqualStringRestriction(String field,
			Object value, boolean currentDisjuntion) {
		this.getQueryOperator().addRestriction(
				new TempLeastEqualStringRestriction(field, value),
				currentDisjuntion);
		return this;
	}

	public GenericService addGreaterEqualRestriction(String field,
			Object value, boolean currentDisjuntion) {
		this.getQueryOperator().addRestriction(
				new GreaterEqualRestriction(field, value), currentDisjuntion);
		return this;
	}

	public GenericService addIsNotNullRestriction(String field,
			boolean operador, boolean currentDisjuntion) {
		this.getQueryOperator().addRestriction(
				new IsNotNullRestriction(field, operador), currentDisjuntion);
		return this;
	}

	public GenericService addLeastRestriction(String field,
			Object value, boolean currentDisjuntion) {
		this.getQueryOperator().addRestriction(
				new LeastRestriction(field, value), currentDisjuntion);
		return this;
	}

	public GenericService addLikeRestriction(String field, String value,
			boolean currentDisjuntion) {
		this.getQueryOperator().addRestriction(
				new LikeRestriction(field, value), currentDisjuntion);
		return this;
	}

	/**
	 * el macthmode puede tomar determinados valores
	 * 
	 * @param field
	 * @param value
	 * @param matchMode
	 *            : ANYWHERE, END, EXACT,START
	 */
	public GenericService addLikeRestriction(String field, String value,
			String matchMode, boolean currentDisjuntion) {
		this.getQueryOperator()
				.addRestriction(new LikeRestriction(field, value, matchMode),
						currentDisjuntion);
		return this;
	}

	public GenericService addSqlRestriction(String sql,
			boolean currentDisjuntion) {
		this.getQueryOperator().addRestriction(new SqlRestriction(null, sql),
				currentDisjuntion);
		return this;
	}

	public GenericService addBetweenRestriction(String field,
			Object start, Object end, boolean currentDisjuntion) {
		this.getQueryOperator().addRestriction(
				new BetweenRestriction(field, start, end), currentDisjuntion);
		return this;
	}

	public GenericService addBetweenRestriction(String field,
			Collection collection, boolean currentDisjuntion) {
		this.getQueryOperator().addRestriction(
				new InsideCollectionRestriction(field, collection),
				currentDisjuntion);
		return this;
	}

	

	
	

	
	
}
